Run these first:
teams auth doctor --output json
teams auth status --output json
teams config show --output jsonAdd verbose stderr logs:
RUST_LOG=teams=debug teams chat list --output jsonMeaning: the keyring access token is expired and could not be refreshed automatically.
The CLI now silently redeems the stored refresh token when the access token is expired or about to expire, so this error normally only appears when no refresh token is stored or the refresh request is rejected (for example the refresh token expired or was revoked).
Resolution:
teams auth login --device-codeCheck:
- Tenant allows user consent or admin consent has been granted.
- The OSO app is allowed by enterprise app policy.
- BYO profile has
auth_app = "byo"and a validclient_id. - Browser PKCE redirect
http://localhost:8400/callbackis allowed. - Device-code login is used for SSH or remote terminals.
Useful command:
teams auth consent-url --tenant-id <tenant-id-or-domain> --output jsonCommon causes:
- Admin consent has not been granted.
- The signed-in user is not a member of the team, channel, chat, or meeting roster.
- The Graph API does not support the operation with the token type used.
- A Teams policy blocks the action.
- Client credentials were used for a delegated-only operation.
A PERMISSION_DENIED now carries a Hint: naming the permissions the token
actually holds — its delegated scopes, or its application roles when the token
is app-only. Graph often answers 403 with an empty message, so this is
frequently the only thing to go on: compare the list against the permission the
operation needs. An opaque (non-JWT) token yields no claims and gets no hint.
Check token type:
teams auth doctor --output json | jq '.data.token.auth_type'For normal message sends, this must be delegated.
Message mutation commands require delegated auth for normal live Teams usage:
teams auth login --device-codeDo not use:
teams auth login --client-credentialsfor normal chat/channel message posting.
This can happen with meeting chats. The chat can appear in the list, but message reads fail if the signed-in user is not in the roster for that thread.
Agent behavior:
- Log the chat ID and Graph request ID.
- Skip that chat.
- Continue processing other chats.
Do not treat one inaccessible chat as a global auth failure.
team list uses /me/joinedTeams. It returns teams where the user is a direct member. It does not mean the user has no chats, meetings, or shared-channel context.
Try:
teams chat list --output json
teams search teams --query "<name>" --output jsonMicrosoft Graph currently says /me/joinedTeams does not support OData query parameters to customize the response. The CLI avoids $top for this endpoint.
Symptoms:
- macOS asks for the login keychain password when running
teams, sometimes on every invocation. - Clicking "Always Allow" does not stop the prompts.
- Prompts return after upgrading the CLI or building it from source.
Cause: macOS binds keychain access grants to the exact code signature of the binary. The released binaries are ad-hoc signed, so every upgrade produces what macOS considers a different application, and a grant made for one build does not carry over to the next. Local builds from source each count as another distinct application for the same reason. The stored items are visible with:
security find-generic-password -s teams-cliDebug builds from source keep their tokens under the separate service
teams-cli-dev (see "Storage namespace" in CONTRIBUTING.md), so rebuilding
from source does not, by default, touch the installed release's items. teams --version
names the namespace of any build that does not use teams-cli.
Actions:
- Expect one prompt per profile after upgrading or rebuilding; approve it and the grant holds until the binary changes again.
- A denied prompt or a locked keychain (common over SSH) fails with
KEYRING_ERROR(exit code 10), not "Not authenticated". Unlock the keychain (security unlock-keychain) or useTEAMS_CLI_TOKEN_STORE=file. - An unattended process (a daemon, a scheduled job) cannot answer the prompt
and waits on it. Set
TEAMS_CLI_TOKEN_STORE=filefor that process and sign in once with it set; tokens then live in0600files under the config directory and no keychain dialog is involved. - Re-signing with a self-signed certificate did not stop the prompts when tested on macOS 26 with this CLI's keychain library: each rebuild signed with the same trusted certificate and identifier was asked again, even after "Always Allow" on the previous one. The signature carries no Apple-issued team identifier, and the keychain appears to tie the item to the build instead.
- In tests only, set
TEAMS_CLI_DISABLE_KEYRING=1so the suite never touches the real keychain.
Do not set TEAMS_CLI_DISABLE_KEYRING=1 for real usage; it prevents token
storage.
Symptoms:
- Auth commands hang.
- Credential Manager prompts appear.
- CI tests block when touching real credentials.
Actions:
- Use a normal user session, not a locked-down service desktop, for interactive login.
- Run PowerShell as the same user that will run
teams. - Check Windows Credential Manager for
teams-clientries. A signed-in profile has a<profile>:tokenheader entry plus<profile>:token:0,<profile>:token:1, ... entries holding the token itself, because Credential Manager caps a single credential at 2560 bytes. Versions up to 0.4.0 wrote one entry and failed withKEYRING_ERROR ... longer than platform limit of 2560 charswhenever the token bundle was larger than that. - In tests only, set
TEAMS_CLI_DISABLE_KEYRING=1.
PowerShell:
$env:TEAMS_CLI_DISABLE_KEYRING = "1"
cargo test --all-targets --all-featuresDo not set TEAMS_CLI_DISABLE_KEYRING=1 for real usage; it prevents token storage.
The keyring crate usually needs a Secret Service-compatible backend.
In headless Linux CI, prefer one of:
- Use
TEAMS_CLI_ACCESS_TOKENfor short-lived tests. - Run CLI tests with
TEAMS_CLI_DISABLE_KEYRING=1. - Install and configure a keyring backend if testing real login.
- On a server or in a container with no Secret Service, set
TEAMS_CLI_TOKEN_STORE=fileto keep tokens in0600files under the config directory instead.
Browser login uses PKCE and the redirect URI:
http://localhost:8400/callback
Check:
- Port
8400is free. - Local firewall allows loopback.
- BYO app registration includes that redirect URI.
- Use
--device-codeif a browser callback is not possible.
Exit code 6 means rate limited after retries.
The shared Graph client honors Retry-After when Microsoft Graph sends it. For agents, add higher-level backoff and avoid parallel fan-out into large tenants.
Show the current file path:
teams config pathInspect config:
teams config show --output jsonRecreate a minimal config:
mv "$(teams config path --output plain | awk '/path:/ {print $2}')" config.toml.bak
teams config initteams file download writes raw bytes to stdout when --path is omitted. Use:
teams file download --team "$TEAM_ID" --channel "$CHANNEL_ID" --file-id "$FILE_ID" --path ./download.binMicrosoft Graph requires a public HTTPS notification URL. teams listen is local HTTP.
For local testing:
teams listen --port 8080Expose it with a trusted HTTPS tunnel, then create the subscription with the tunnel URL.