Skip to content

refactor: optimize refresh token error handling and enhance file lock and token file checks - #2135

Open
kiraWangRuilong wants to merge 2 commits into
mainfrom
refactor/optimize-refresh-token-flow
Open

refactor: optimize refresh token error handling and enhance file lock and token file checks#2135
kiraWangRuilong wants to merge 2 commits into
mainfrom
refactor/optimize-refresh-token-flow

Conversation

@kiraWangRuilong

@kiraWangRuilong kiraWangRuilong commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR contains two related follow-up changes to user access token refresh reliability:

  1. Optimize refresh token error handling and classification (retry/policy/storage behavior based on structured server codes).
  2. Harden refresh lock/token handling by using lock-directory write prechecks and CAS-style generation checks under concurrent refresh scenarios.

Changes

  • Scope error handling of /open-apis/authen/v2/oauth/token refresh flow:
  • Extended error-code metadata mapping in internal/errclass/codemeta.go for OAuth refresh-related and validation/client config/user/app-state codes, with retryability updated where applicable.
  • Introduced generation-safe token-store operations in internal/auth/token_store.go:
  • Updated refresh flow to use cross-process safe lock and generation-safe operations in internal/auth/uat_client.go:
  • Added/updated tests covering refresh lock + generation and writeability behavior, including non-writable lock directory paths returning File I/O errors.

Test Plan

  • Unit tests pass
  • Manual local verification confirms the flow works as expected

Related Issues

  • None

Summary by CodeRabbit

  • Bug Fixes
    • Improved token refresh reliability during concurrent operations.
    • Prevented newer saved credentials from being overwritten or removed.
    • Added validation for token refresh responses and clearer handling of authentication and authorization errors.
    • Improved retry behavior for temporary versus permanent refresh failures.
  • Error Handling
    • Expanded error classification for invalid requests, client configuration, authorization, and refresh-token conditions.

@github-actions github-actions Bot added the size/XL Architecture-level or global-impact change label Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The token refresh flow now reloads state under global locks, classifies JSON refresh responses, handles retries and policy errors, checks directory writeability, and conditionally persists or removes tokens by generation. Error metadata and tests cover additional authentication, authorization, API, and configuration codes.

Changes

Token refresh flow

Layer / File(s) Summary
Token generation and conditional storage
internal/auth/token_store.go
Internal token reads now return errors. Token updates and removals compare token generations before changing stored state.
Locked refresh state handling
internal/auth/uat_client.go
Refresh operations acquire global locks, reload token state, handle concurrent changes, and check lock-directory writeability.
Classified refresh and persistence
internal/auth/uat_client.go
Refresh requests use JSON and tracing. Responses receive typed validation, retry classification, fallback expiration handling, and generation-safe persistence.
Refresh error metadata coverage
internal/errclass/codemeta.go, internal/errclass/codemeta_test.go
Additional authentication, authorization, API, and configuration codes receive classifications and table-driven test coverage.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant refreshWithLock
  participant TokenStore
  participant RefreshEndpoint
  participant ErrorClassifier
  refreshWithLock->>TokenStore: reload token after acquiring global lock
  TokenStore-->>refreshWithLock: current token generation
  refreshWithLock->>RefreshEndpoint: send JSON refresh request
  RefreshEndpoint-->>refreshWithLock: traced response
  refreshWithLock->>ErrorClassifier: classify response and retry action
  ErrorClassifier-->>refreshWithLock: retry or preservation decision
  refreshWithLock->>TokenStore: conditionally update or remove token
  TokenStore-->>refreshWithLock: current or persisted token
Loading

Suggested reviewers: liangshuo-1

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes to refresh-token error handling, file locking, and token-file checks.
Description check ✅ Passed The description includes all required sections and explains the refresh-flow, locking, token-generation, testing, and issue scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/optimize-refresh-token-flow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kiraWangRuilong kiraWangRuilong changed the title refactor: Optimize refresh token error handling and enhance file lock and token file checks refactor: optimize refresh token error handling and enhance file lock and token file checks Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

PR Quality Summary

CI did not complete successfully. Use the failed check links below to decide whether this PR needs a code change or a rerun.

Failed checks

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
internal/auth/token_store.go (1)

47-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider returning typed errors from readStoredToken.

readStoredToken returns raw keychain and json.Unmarshal errors. These errors now reach callers such as refreshWithLock and surface untyped. Wrap them with the prescribed typed constructor so the CLI emits a classified envelope.

♻️ Proposed refactor
 func readStoredToken(appId, userOpenId string) (*StoredUAToken, error) {
 	jsonStr, err := keychain.Get(keychain.LarkCliService, accountKey(appId, userOpenId))
 	if err != nil {
-		return nil, err
+		return nil, errs.NewInternalError(errs.SubtypeStorage,
+			"failed to read stored user token: %v", err).WithCause(err)
 	}
 	if jsonStr == "" {
 		return nil, nil
 	}
 	var token StoredUAToken
 	if err := json.Unmarshal([]byte(jsonStr), &token); err != nil {
-		return nil, err
+		return nil, errs.NewInternalError(errs.SubtypeStorage,
+			"stored user token is not valid JSON: %v", err).WithCause(err)
 	}
 	return &token, nil
 }

As per coding guidelines: "Use the prescribed typed error constructors for validation, failed preconditions, API failures, network failures, file I/O failures, and unknown lower-layer errors".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/auth/token_store.go` around lines 47 - 59, Update readStoredToken to
wrap both keychain.Get and json.Unmarshal failures with the prescribed typed
error constructor before returning them, while preserving nil-token behavior for
an empty keychain value and successful token parsing.

Source: Coding guidelines

internal/auth/uat_client.go (1)

248-259: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: remove the duplicated refresh-expiry check.

refreshWithLock already handles the expired status under the lock at Lines 169-181, including removeStoredTokenIfCurrent and the same log line. This block repeats that logic for the same token snapshot. Consider keeping the check in one place, or add a comment that states which callers can reach doRefreshToken without the locked check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/auth/uat_client.go` around lines 248 - 259, Remove the duplicated
refresh-expiry handling from doRefreshToken, relying on refreshWithLock’s
existing locked check and cleanup flow. If the check must remain for callers
that bypass refreshWithLock, document that caller path clearly instead of
duplicating the logic.
internal/errclass/codemeta.go (1)

56-62: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the comment typos.

Line 60 has staus. Line 62 reads not allows for refresh token.

✏️ Proposed fix
-	20066:    {Category: errs.CategoryAuthorization, Subtype: errs.SubtypeUserUnauthorized}, // user staus is not normal
+	20066:    {Category: errs.CategoryAuthorization, Subtype: errs.SubtypeUserUnauthorized}, // user status is not normal
-	20074:    {Category: errs.CategoryAuthorization, Subtype: errs.SubtypeAppUnavailable},   // app specified not allows for refresh token
+	20074:    {Category: errs.CategoryAuthorization, Subtype: errs.SubtypeAppUnavailable},   // app is not allowed to use refresh tokens
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/errclass/codemeta.go` around lines 56 - 62, Correct the inline
comments in the error-code mapping: change “staus” to “status” on the 20066
entry and fix the grammar of the 20074 comment to state that the app does not
allow refresh tokens.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/auth/token_store.go`:
- Around line 77-80: Update the comment above isSameStoredTokenGeneration to use
the exact function name, remove the trailing whitespace after “does not,” and
run gofmt so the repository produces no formatting violations.

In `@internal/auth/uat_client.go`:
- Around line 469-481: Update refreshActionForCode so the !ok branch for
unmapped codes preserves the stored token while retaining retry behavior. Keep
token clearing limited to explicitly classified terminal credential failures,
without changing the existing policy, retryable, or default classified-code
handling.
- Around line 396-416: Update the policy-error construction in the refresh
result branch to ensure Problem.Message is non-empty when
parsed.ErrorDescription is absent. Reuse the established errclass fallback
behavior or generated-message helper used by errclass.BuildAPIError, while
preserving the endpoint description when provided; only adjust the Message
assignment in the errs.SecurityPolicyError path.

In `@internal/errclass/codemeta.go`:
- Around line 44-46: Update the codemeta entry for error code 20072 to set
Retryable: true, matching the temporary refresh-server behavior and the 20050
entry. Update the corresponding expected retryability assertion in the codemeta
tests to true.

---

Nitpick comments:
In `@internal/auth/token_store.go`:
- Around line 47-59: Update readStoredToken to wrap both keychain.Get and
json.Unmarshal failures with the prescribed typed error constructor before
returning them, while preserving nil-token behavior for an empty keychain value
and successful token parsing.

In `@internal/auth/uat_client.go`:
- Around line 248-259: Remove the duplicated refresh-expiry handling from
doRefreshToken, relying on refreshWithLock’s existing locked check and cleanup
flow. If the check must remain for callers that bypass refreshWithLock, document
that caller path clearly instead of duplicating the logic.

In `@internal/errclass/codemeta.go`:
- Around line 56-62: Correct the inline comments in the error-code mapping:
change “staus” to “status” on the 20066 entry and fix the grammar of the 20074
comment to state that the app does not allow refresh tokens.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 78bff60f-ccc0-4b45-b223-ad1e1e9fffa0

📥 Commits

Reviewing files that changed from the base of the PR and between 7946e5c and 844c6eb.

📒 Files selected for processing (4)
  • internal/auth/token_store.go
  • internal/auth/uat_client.go
  • internal/errclass/codemeta.go
  • internal/errclass/codemeta_test.go

Comment on lines +77 to +80
// sameStoredTokenGeneration reports whether two snapshots represent the same
// refresh-token generation. Access tokens are used only for case that does not
// contain a refresh token.
func isSameStoredTokenGeneration(current, expected *StoredUAToken) bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the doc comment name and remove the trailing space.

The comment names sameStoredTokenGeneration, but the function is isSameStoredTokenGeneration. Line 78 also ends with a trailing space after does not , which gofmt removes. The repository requires no gofmt -l . output.

♻️ Proposed fix
-// sameStoredTokenGeneration reports whether two snapshots represent the same
-// refresh-token generation. Access tokens are used only for case that does not 
-// contain a refresh token.
+// isSameStoredTokenGeneration reports whether two snapshots represent the same
+// refresh-token generation. Access tokens are compared only when neither
+// snapshot contains a refresh token.
 func isSameStoredTokenGeneration(current, expected *StoredUAToken) bool {

As per coding guidelines: "Run gofmt; Go code must be formatted with no gofmt -l . output."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// sameStoredTokenGeneration reports whether two snapshots represent the same
// refresh-token generation. Access tokens are used only for case that does not
// contain a refresh token.
func isSameStoredTokenGeneration(current, expected *StoredUAToken) bool {
// isSameStoredTokenGeneration reports whether two snapshots represent the same
// refresh-token generation. Access tokens are compared only when neither
// snapshot contains a refresh token.
func isSameStoredTokenGeneration(current, expected *StoredUAToken) bool {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/auth/token_store.go` around lines 77 - 80, Update the comment above
isSameStoredTokenGeneration to use the exact function name, remove the trailing
whitespace after “does not,” and run gofmt so the repository produces no
formatting violations.

Source: Coding guidelines

Comment on lines +396 to 416
if code != 0 {
if meta, ok := errclass.LookupCodeMeta(code); ok && meta.Category == errs.CategoryPolicy {
var policyFields struct {
ChallengeURL string `json:"challenge_url"`
CLIHint string `json:"cli_hint"`
}
// Retry succeeded, fall through to parse token below.
} else {
// All other errors: clear token, require re-authorization.
fmt.Fprintf(errOut, "[lark-cli] [WARN] uat-client: refresh failed (code=%d), clearing token for %s\n", code, opts.UserOpenId)
if err := RemoveStoredToken(opts.AppId, opts.UserOpenId); err != nil {
fmt.Fprintf(errOut, "[lark-cli] [WARN] uat-client: failed to remove token: %v\n", err)
_ = json.Unmarshal(body, &policyFields)
return refreshResult{
action: refreshStopAndPreserve,
err: &errs.SecurityPolicyError{
Problem: errs.Problem{
Category: errs.CategoryPolicy,
Subtype: meta.Subtype,
Code: code,
Message: parsed.ErrorDescription,
Hint: policyFields.CLIHint,
},
ChallengeURL: policyFields.ChallengeURL,
},
}
return nil, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Keep Problem.Message non-empty for policy errors.

This branch sets Message from parsed.ErrorDescription only. The OAuth endpoint can omit error_description. Message is then empty on the wire envelope. errclass.BuildAPIError avoids this by falling back to a generated message (see internal/errclass/classify.go:54-59). Apply the same fallback here.

🛠️ Proposed fix
+			message := parsed.ErrorDescription
+			if message == "" {
+				message = parsed.Error
+			}
+			if message == "" {
+				message = fmt.Sprintf("token refresh rejected by policy: [%d]", code)
+			}
 			return refreshResult{
 				action: refreshStopAndPreserve,
 				err: &errs.SecurityPolicyError{
 					Problem: errs.Problem{
 						Category: errs.CategoryPolicy,
 						Subtype:  meta.Subtype,
 						Code:     code,
-						Message:  parsed.ErrorDescription,
+						Message:  message,
 						Hint:     policyFields.CLIHint,
 					},
 					ChallengeURL: policyFields.ChallengeURL,
 				},
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if code != 0 {
if meta, ok := errclass.LookupCodeMeta(code); ok && meta.Category == errs.CategoryPolicy {
var policyFields struct {
ChallengeURL string `json:"challenge_url"`
CLIHint string `json:"cli_hint"`
}
// Retry succeeded, fall through to parse token below.
} else {
// All other errors: clear token, require re-authorization.
fmt.Fprintf(errOut, "[lark-cli] [WARN] uat-client: refresh failed (code=%d), clearing token for %s\n", code, opts.UserOpenId)
if err := RemoveStoredToken(opts.AppId, opts.UserOpenId); err != nil {
fmt.Fprintf(errOut, "[lark-cli] [WARN] uat-client: failed to remove token: %v\n", err)
_ = json.Unmarshal(body, &policyFields)
return refreshResult{
action: refreshStopAndPreserve,
err: &errs.SecurityPolicyError{
Problem: errs.Problem{
Category: errs.CategoryPolicy,
Subtype: meta.Subtype,
Code: code,
Message: parsed.ErrorDescription,
Hint: policyFields.CLIHint,
},
ChallengeURL: policyFields.ChallengeURL,
},
}
return nil, nil
}
if code != 0 {
if meta, ok := errclass.LookupCodeMeta(code); ok && meta.Category == errs.CategoryPolicy {
var policyFields struct {
ChallengeURL string `json:"challenge_url"`
CLIHint string `json:"cli_hint"`
}
_ = json.Unmarshal(body, &policyFields)
message := parsed.ErrorDescription
if message == "" {
message = parsed.Error
}
if message == "" {
message = fmt.Sprintf("token refresh rejected by policy: [%d]", code)
}
return refreshResult{
action: refreshStopAndPreserve,
err: &errs.SecurityPolicyError{
Problem: errs.Problem{
Category: errs.CategoryPolicy,
Subtype: meta.Subtype,
Code: code,
Message: message,
Hint: policyFields.CLIHint,
},
ChallengeURL: policyFields.ChallengeURL,
},
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/auth/uat_client.go` around lines 396 - 416, Update the policy-error
construction in the refresh result branch to ensure Problem.Message is non-empty
when parsed.ErrorDescription is absent. Reuse the established errclass fallback
behavior or generated-message helper used by errclass.BuildAPIError, while
preserving the endpoint description when provided; only adjust the Message
assignment in the errs.SecurityPolicyError path.

Comment on lines +469 to +481
func refreshActionForCode(code int) refreshAction {
meta, ok := errclass.LookupCodeMeta(code)
switch {
case !ok:
return refreshRetryAndClear
case meta.Category == errs.CategoryPolicy:
return refreshStopAndPreserve
case meta.Retryable:
return refreshRetryAndPreserve
default:
return refreshStopAndClear
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reconsider clearing the stored token for unmapped server codes.

refreshActionForCode returns refreshRetryAndClear when LookupCodeMeta has no entry for the code. An unrecognized code then deletes the stored refresh token after the last attempt, and the user must log in again. A new or undocumented server code is not evidence that the refresh token is invalid. Prefer preserving the token for unmapped codes, and clear it only for codes that are classified as terminal credential failures.

♻️ Proposed change
 	switch {
 	case !ok:
-		return refreshRetryAndClear
+		return refreshRetryAndPreserve
 	case meta.Category == errs.CategoryPolicy:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func refreshActionForCode(code int) refreshAction {
meta, ok := errclass.LookupCodeMeta(code)
switch {
case !ok:
return refreshRetryAndClear
case meta.Category == errs.CategoryPolicy:
return refreshStopAndPreserve
case meta.Retryable:
return refreshRetryAndPreserve
default:
return refreshStopAndClear
}
}
func refreshActionForCode(code int) refreshAction {
meta, ok := errclass.LookupCodeMeta(code)
switch {
case !ok:
return refreshRetryAndPreserve
case meta.Category == errs.CategoryPolicy:
return refreshStopAndPreserve
case meta.Retryable:
return refreshRetryAndPreserve
default:
return refreshStopAndClear
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/auth/uat_client.go` around lines 469 - 481, Update
refreshActionForCode so the !ok branch for unmapped codes preserves the stored
token while retaining retry behavior. Keep token clearing limited to explicitly
classified terminal credential failures, without changing the existing policy,
retryable, or default classified-code handling.

Comment on lines +44 to +46
20050: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeRefreshServerError, Retryable: true}, // refresh endpoint transient error
20064: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeRefreshTokenRevoked}, // refresh_token revoked
20072: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeRefreshServerError}, // refresh endpoint temporarily unavailable

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Lark open platform OAuth error code 20072 refresh token retryable

💡 Result:

The Lark Open Platform error code 20072 indicates that the server is temporarily unavailable [1]. According to official Lark documentation, this error is categorized as a transient server-side issue, and you are advised to retry your request [1]. Key details regarding this error: * Meaning: The server is temporarily unable to process the request [1]. * Status: It is considered a retryable error [1]. * Implementation: In the context of the Lark Open Platform SDK, transient server-side errors (such as those represented by HTTP 503-like statuses) are explicitly handled as retryable to ensure robust token management [2][3]. If you encounter this error while refreshing a token, you should implement an exponential backoff or a brief delay before retrying the operation to avoid overwhelming the server [1]. If the error persists after multiple retries, it may indicate a broader service disruption, and you should contact Lark technical support or monitor official status channels [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files | rg '(^|/)(codemeta\.go|codemeta_test\.go|refreshActionForCode|errs|subtypes)\.go$' || true

echo
echo "== codemeta.go relevant lines =="
if [ -f internal/errclass/codemeta.go ]; then
  nl -ba internal/errclass/codemeta.go | sed -n '1,120p'
fi

echo
echo "== codemeta_test.go relevant context =="
if [ -f internal/errclass/codemeta_test.go ]; then
  nl -ba internal/errclass/codemeta_test.go | sed -n '1,120p'
fi

echo
echo "== search refreshActionForCode and SubtypeRefreshServerError =="
rg -n "refreshActionForCode|SubtypeRefreshServerError|20072|20050|Retryable" -S .

Repository: larksuite/cli

Length of output: 265


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files | grep -E '(^|/)(codemeta\.go|codemeta_test\.go|refreshActionForCode|errs|subtypes)\.go$' || true

echo
echo "== codemeta.go relevant lines =="
if command -v nl >/dev/null 2>&1; then
  nl -ba internal/errclass/codemeta.go | sed -n '1,120p'
elif command -v awk >/dev/null 2>&1; then
  awk '{printf "%6d\t%s\n", NR, $0}' internal/errclass/codemeta.go | sed -n '1,120p'
fi

echo
echo "== codemeta_test.go relevant context =="
if [ -f internal/errclass/codemeta_test.go ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' internal/errclass/codemeta_test.go | sed -n '1,120p'
fi

echo
echo "== search refreshActionForCode and SubtypeRefreshServerError =="
grep -RIn "refreshActionForCode|SubtypeRefreshServerError|20072|20050|Retryable" . || true

Repository: larksuite/cli

Length of output: 14267


🌐 Web query:

Lark open platform refresh-user-access-token error 20072 retryable

💡 Result:

In the Lark Open Platform API, the error code 20072 indicates that the server is temporarily unavailable [1][2]. The official recommendation for this error is to retry your request [1][2]. Because this is a transient server-side error, it is considered retryable [1][2][3]. If you continue to encounter this error after retrying, it suggests a persistent issue with the server's availability, and you may need to wait before attempting further requests [1][2].

Citations:


Mark refresh error 20072 as retryable.

20072 is documented as a temporary refresh server unavailable error, so it should use Retryable: true like 20050, and the expected retryability in internal/errclass/codemeta_test.go line 31 should also change to true.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/errclass/codemeta.go` around lines 44 - 46, Update the codemeta
entry for error code 20072 to set Retryable: true, matching the temporary
refresh-server behavior and the 20050 entry. Update the corresponding expected
retryability assertion in the codemeta tests to true.

@github-actions

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@844c6eb30fc394727a4a0e6ab83e4d195408dbcb

🧩 Skill update

npx skills add larksuite/cli#refactor/optimize-refresh-token-flow -y -g

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Architecture-level or global-impact change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant