Skip to main content
Outlook Assistant · v3.7.2 minor release ·

Outlook Assistant v3.7.2 — Restart-Safe Device Code Auth (Telegram-Friendly)

Outlook Assistant v3.7.2 persists device code auth state across MCP server restarts and fixes token refresh for public-client tokens. Critical for Telegram bridges.

Highlights

Device code state survives restarts

the pending auth payload now persists to ~/.outlook-assistant-pending-auth.json (mode 0o600). authenticate writes, device-code-complete reads. Works across server crashes, host reloads, and any process boundary.

Token refresh fixed for public clients

device code is a public client flow — Microsoft rejects client_secret on refresh ('Public clients can't send a client secret'). v3.7.2 stores auth_method in the token file and excludes client_secret on refresh for device-code tokens. Browser-flow tokens unchanged.

auth_method tracked per token

every token now records how it was obtained — auth_method: device-code or browser. The refresh logic branches on this so each flow uses the right OAuth contract.

What’s new in v3.7.2

This release exists because of Untether. Untether’s Telegram bridge restarts coding agents (and their MCP servers) between tool calls — that’s part of how it works. Outlook Assistant’s device code auth flow assumed the server stayed up between authenticate and device-code-complete. It didn’t.

The bug: pending auth state was in-memory only

Microsoft’s device code flow has two server calls. The first (authenticate) returns a user code and a polling URL. You go to the URL on a browser, type the code, sign in. The second (device-code-complete) fires when you tell the agent you’re done, which exchanges the device code for a real access token.

In a normal MCP setup, those two calls happen in the same process, seconds apart. The pending state lived in a module-level variable. Fine.

In Untether (and similar agent-host environments), the MCP server may restart in between — process boundaries, idle timeouts, anything. The pending state vanished, and device-code-complete failed with “no pending device code flow”, forcing you to start over. Sometimes you’d start over and the same thing would happen.

The fix: persist to disk

v3.7.2 writes the pending auth payload to ~/.outlook-assistant-pending-auth.json with mode 0o600 (owner read/write only). authenticate writes; device-code-complete reads first, falls back to in-memory if the file isn’t there. Survives restarts, crashes, host reloads, anything that doesn’t rm the file.

The token refresh bug

Once you actually had a token, refreshing it broke if you’d authenticated via device code. Microsoft’s OAuth treats device code as a public client flow — no client_secret allowed on refresh. The previous refresh code always sent the secret, so refreshes failed with “Public clients can’t send a client secret” and the user had to re-authenticate every time the access token expired.

v3.7.2 stores auth_method: "device-code" (or "browser") in the token file and branches on it. Device code tokens refresh without the secret; browser tokens refresh with it. Both work indefinitely now.

About re-authenticating

Tokens issued before v3.7.2 don’t carry auth_method, so the first refresh after upgrading defaults to the browser-flow contract. If your tokens came from device code, that first refresh will fail and you’ll need to re-authenticate once. The fresh token gets tagged correctly and stays good thereafter.

I built this for Untether but anyone running MCP servers in environments with process restarts — orchestrators, sandbox managers, headless deployments — benefits.

Frequently asked questions

Why did device code auth break across server restarts?
The pending device code response (user_code, device_code, expiry) was held in module-level memory. Microsoft's authenticate / device-code-complete handshake usually happens within seconds, but in MCP host environments — especially Untether's Telegram bridge — the server restarts between tool calls. The completion step then had nothing to complete with. v3.7.2 persists the payload to disk (mode 0o600) so the second call always finds it, even after a crash and rehydrate.
What was the token refresh bug?
Microsoft treats the device code flow as a public client (no client secret). Refreshing a token obtained that way fails if you include client_secret — Microsoft replies 'Public clients can't send a client secret'. The previous refresh code unconditionally included it, so any device-code token that needed refreshing started bouncing on the refresh and the user had to re-authenticate. v3.7.2 stores auth_method on the token file and conditionally excludes client_secret on refresh for device-code tokens.
Do I need to re-authenticate?
Existing tokens issued before v3.7.2 don't have auth_method set, so the first refresh after upgrading will treat them as confidential clients (browser flow). If you authenticated via device code on a previous version, it's safer to re-authenticate once — the new token will be tagged correctly and refresh forever after.