From b45dc2dc42a3dcb0154d3bdeca0ddae5e9c20116 Mon Sep 17 00:00:00 2001 From: Koshkoshinsk Date: Mon, 6 Jul 2026 13:52:05 +0300 Subject: [PATCH] add-teams: sign out of the Teams CLI once the bot exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The M365 session is only needed to create the bot — the adapter runs on the .env app credentials. On a headless box the session is a plaintext token file, so setup no longer leaves it on disk. teams logout is idempotent; later CLI maintenance just needs a fresh teams login (device code). Co-Authored-By: Claude Fable 5 --- .claude/skills/add-teams/SKILL.md | 14 ++++++++++++++ setup/channels/run-channel-skill.test.ts | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/.claude/skills/add-teams/SKILL.md b/.claude/skills/add-teams/SKILL.md index 0de90d9ce..1430fd876 100644 --- a/.claude/skills/add-teams/SKILL.md +++ b/.claude/skills/add-teams/SKILL.md @@ -215,6 +215,20 @@ Install the bot into Teams: Once the app shows up in your Teams sidebar (or app list), continue. ``` +### Sign out of the Teams CLI + +The Microsoft 365 session was only needed to create the bot — the running +adapter authenticates with the app credentials in `.env`, never with your +account. On a headless box that session is a plaintext token file, so it +doesn't stay on disk once setup is done. Idempotent (already signed out is a +no-op). Any `teams …` command you run later — the Troubleshooting recovery +commands, `teams app rsc add`, an endpoint update — just needs a fresh +`teams login` first (a ~30-second device code). + +```nc:run effect:external when:have_creds=no +"$(npm prefix -g 2>/dev/null)/bin/teams" logout +``` + ## Restart Restart the service so it loads the Teams adapter and the credentials you just diff --git a/setup/channels/run-channel-skill.test.ts b/setup/channels/run-channel-skill.test.ts index b4e300db0..294c01955 100644 --- a/setup/channels/run-channel-skill.test.ts +++ b/setup/channels/run-channel-skill.test.ts @@ -126,6 +126,9 @@ describe('runChannelSkill adapter (Option A)', () => { expect(log.some((c) => c.includes('pnpm add @chat-adapter/teams'))).toBe(true); expect(log.some((c) => c.includes('app create'))).toBe(false); expect(log.some((c) => c.includes('login'))).toBe(false); + // …no logout either — the drop-through path never signed in, and must not + // sign out a session the operator may be using for something else… + expect(log.some((c) => c.includes('logout'))).toBe(false); // …the Teams CLI install is also skipped (nothing to create)… expect(log.some((c) => c.includes('npm install -g @microsoft/teams.cli'))).toBe(false); // …the service still restarts (adapter + existing credentials load)… @@ -229,6 +232,9 @@ describe('runChannelSkill adapter (Option A)', () => { // the prompted name, and the unconditional single-tenant default… expect(log.some((c) => c.includes('--endpoint "https://acme.example/webhook/teams"'))).toBe(true); expect(log.some((c) => c.includes('--name "NanoClaw"') && c.includes('--sign-in-audience myOrg'))).toBe(true); + // …the M365 session was signed out once the bot existed (the adapter runs + // on the .env app credentials; the plaintext token cache must not linger)… + expect(log.some((c) => c.includes('/bin/teams" logout'))).toBe(true); // …the captured credentials landed in .env with the safe SingleTenant pairing… const env = readFileSync(join(root, '.env'), 'utf8'); expect(env).toContain('TEAMS_APP_ID=12345678-1234-1234-1234-123456789abc');