Skip to content

Device flow grants offline_access in scope but returns no refresh_token (re-auth required every ~2h) #2049

Description

@morefreefg

Describe the bug

Every device-flow login succeeds and stores an access token whose granted scope includes offline_access, but the token response never contains a refresh_token. The CLI prints [WARN] device-flow: no refresh_token in response, sets refreshExpiresAt == expiresAt, and silent renewal is therefore impossible — the user has to re-scan a QR code every ~2 hours.

Environment

  • lark-cli 1.0.76 (npm @larksuite/cli), macOS arm64 (Darwin 25.5.0)
  • brand: feishu
  • App: created via the CLI app registration flow (/oauth/v1/app/registration), app_id cli_a8cd685509bd1013
  • Developer console shows the offline_access permission (持续访问已授权的数据, user identity) as 已开通/enabled

What was ruled out before filing

  1. Missing scope in the request — the CLI always auto-appends offline_access to the device authorization scope (internal/auth/device_flow.go:72), and one of the logins below additionally passed an explicit --scope offline_access.
  2. Console permission not enabled — the console shows offline_access as enabled for user identity.
  3. Server not granting the scope — after each login, auth status shows the stored scope ending with offline_access. The stored scope is taken from the token response's scope field (cmd/auth/login.go stores result.Token.Scope), so the server itself reports offline_access as granted.
  4. General device-flow limitation — other users on the same flow clearly receive ~7-day refresh tokens (see the timelines in auth login does not use refresh token to renew access token #738), so this looks app- or tenant-specific.

Docs say refresh_token "仅在请求成功且用户授予 offline_access 权限时返回" — both conditions appear satisfied here, yet the field is absent.

Sanitized log lines (all three token exchanges today)

[lark-cli] auth-response: time=2026-07-24T02:38:26-07:00 path=/open-apis/authen/v2/oauth/token status=200 x-tt-logid=202607241738257A6928EAA624D4DFABFF
[lark-cli] auth-response: time=2026-07-24T17:44:06+08:00 path=/open-apis/authen/v2/oauth/token status=200 x-tt-logid=20260724174405F0388D052CB779DD0C0C
[lark-cli] auth-response: time=2026-07-24T02:51:34-07:00 path=/open-apis/authen/v2/oauth/token status=200 x-tt-logid=20260724175134296A2A4D555E7EDB9237

Each of these logins printed:

[lark-cli] device-flow: token response received
[lark-cli] [WARN] device-flow: no refresh_token in response

and resulted in refreshExpiresAt exactly equal to expiresAt (grantedAt + 2h).

Questions

  1. Under what server-side conditions does /open-apis/authen/v2/oauth/token (grant_type urn:ietf:params:oauth:grant-type:device_code) omit refresh_token while still including offline_access in the returned scope?
  2. Does enabling offline_access in the console require publishing a new app version before the token endpoint starts issuing refresh tokens? If so, could the console/docs/CLI surface this more clearly?
  3. Could this be specific to apps created through the CLI app-registration flow?

Happy to provide the full ~/.lark-cli/logs/auth-*.log privately if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain/authAuthentication subsystem

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions