Device flow grants offline_access in scope but returns no refresh_token (re-auth required every ~2h)
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 17.3k
- Forks
- 1.4k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 105
Description
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_idcli_a8cd685509bd1013 - Developer console shows the
offline_accesspermission (持续访问已授权的数据, user identity) as 已开通/enabled
What was ruled out before filing
- Missing scope in the request — the CLI always auto-appends
offline_accessto the device authorization scope (internal/auth/device_flow.go:72), and one of the logins below additionally passed an explicit--scope offline_access. - Console permission not enabled — the console shows
offline_accessas enabled for user identity. - Server not granting the scope — after each login,
auth statusshows the stored scope ending withoffline_access. The stored scope is taken from the token response'sscopefield (cmd/auth/login.gostoresresult.Token.Scope), so the server itself reportsoffline_accessas granted. - General device-flow limitation — other users on the same flow clearly receive ~7-day refresh tokens (see the timelines in #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
- Under what server-side conditions does
/open-apis/authen/v2/oauth/token(grant_typeurn:ietf:params:oauth:grant-type:device_code) omitrefresh_tokenwhile still includingoffline_accessin the returnedscope? - Does enabling
offline_accessin 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? - 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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with internal/auth/device_flow.go:72 and cmd/auth/login.go to trace the requested and returned scopes, refresh_token handling, and refreshExpiresAt calculation. Compare the device-flow behavior with the timelines in #738 and inspect the sanitized token responses. Done means the server-side condition or app-registration/configuration cause is identified and the required behavior or documentation change is specified.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, authentication, cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100