Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions cmd/auth/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,14 @@
return nil
}

app := multi.CurrentAppConfig(f.Invocation.Profile)
if app == nil || len(app.Users) == 0 {
// A selector that matches no profile is an input error, not an empty
// account: reporting it as not_logged_in (exit 0) would steer the caller
// into auth login against a profile that does not exist.
app, err := multi.RequireAppConfig(f.Invocation.Profile, f.Invocation.ProfileSource)
if err != nil {
return err

Check warning on line 91 in cmd/auth/list.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/list.go#L91

Added line #L91 was not covered by tests
}
if len(app.Users) == 0 {
if opts.JSON {
output.PrintJson(f.IOStreams.Out, map[string]interface{}{
"ok": true,
Expand Down
10 changes: 8 additions & 2 deletions cmd/auth/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,14 @@
return nil
}

app := multi.CurrentAppConfig(f.Invocation.Profile)
if app == nil || len(app.Users) == 0 {
// A selector that matches no profile is an input error, not a logged-out
// state: "not_logged_in" (exit 0) would hide a stale --profile or
// LARKSUITE_CLI_PROFILE value from the caller.
app, err := multi.RequireAppConfig(f.Invocation.Profile, f.Invocation.ProfileSource)
if err != nil {
return err

Check warning on line 66 in cmd/auth/logout.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/logout.go#L66

Added line #L66 was not covered by tests
}
if len(app.Users) == 0 {
if opts.JSON {
output.PrintJson(f.IOStreams.Out, map[string]interface{}{
"ok": true,
Expand Down
21 changes: 20 additions & 1 deletion cmd/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ package cmd
import (
"errors"
"io"
"os"

"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/core"
"github.com/larksuite/cli/internal/envvars"
"github.com/spf13/pflag"
)

Expand All @@ -26,5 +29,21 @@ func BootstrapInvocationContext(args []string) (cmdutil.InvocationContext, error
if err := fs.Parse(args); err != nil && !errors.Is(err, pflag.ErrHelp) {
return cmdutil.InvocationContext{}, err
}
return cmdutil.InvocationContext{Profile: globals.Profile}, nil
// Resolve the session-level default only at the process boundary. Core
// config and credential packages consume the immutable invocation context
// and remain independent of ambient environment state. An explicitly empty
// --profile= remains a flag selection and suppresses the environment value.
if fs.Changed("profile") {
return cmdutil.InvocationContext{
Profile: globals.Profile,
ProfileSource: core.ProfileFromFlag,
}, nil
}
if profile := os.Getenv(envvars.CliProfile); profile != "" {
return cmdutil.InvocationContext{
Profile: profile,
ProfileSource: core.ProfileFromEnvironment,
}, nil
}
return cmdutil.InvocationContext{ProfileSource: core.ProfileFromConfig}, nil
}
31 changes: 31 additions & 0 deletions cmd/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ package cmd
import (
"errors"
"testing"

"github.com/larksuite/cli/internal/core"
"github.com/larksuite/cli/internal/envvars"
)

func TestBootstrapInvocationContext_ProfileFlag(t *testing.T) {
Expand Down Expand Up @@ -45,6 +48,7 @@ func TestBootstrapInvocationContext_MissingProfileValue(t *testing.T) {
}

func TestBootstrapInvocationContext_HelpFlag(t *testing.T) {
t.Setenv(envvars.CliProfile, "")
inv, err := BootstrapInvocationContext([]string{"--help"})
if err != nil {
t.Fatalf("--help should not error, got: %v", err)
Expand All @@ -55,6 +59,7 @@ func TestBootstrapInvocationContext_HelpFlag(t *testing.T) {
}

func TestBootstrapInvocationContext_ShortHelp(t *testing.T) {
t.Setenv(envvars.CliProfile, "")
inv, err := BootstrapInvocationContext([]string{"-h"})
if err != nil {
t.Fatalf("-h should not error, got: %v", err)
Expand All @@ -74,6 +79,32 @@ func TestBootstrapInvocationContext_HelpWithProfile(t *testing.T) {
}
}

func TestBootstrapInvocationContext_ProfilePrecedence(t *testing.T) {
for _, tc := range []struct {
name string
environment string
args []string
wantProfile string
wantSource core.ProfileSource
}{
{"environment default", "session", []string{"whoami"}, "session", core.ProfileFromEnvironment},
{"flag overrides environment", "session", []string{"whoami", "--profile", "command"}, "command", core.ProfileFromFlag},
{"empty flag suppresses environment", "session", []string{"whoami", "--profile="}, "", core.ProfileFromFlag},
{"empty environment is unset", "", []string{"whoami"}, "", core.ProfileFromConfig},
} {
t.Run(tc.name, func(t *testing.T) {
t.Setenv(envvars.CliProfile, tc.environment)
inv, err := BootstrapInvocationContext(tc.args)
if err != nil {
t.Fatalf("BootstrapInvocationContext() error = %v", err)
}
if inv.Profile != tc.wantProfile || inv.ProfileSource != tc.wantSource {
t.Fatalf("profile = %q, source = %v, want %q / %v", inv.Profile, inv.ProfileSource, tc.wantProfile, tc.wantSource)
}
})
}
}

func TestIsDeferredBootstrapProfileError(t *testing.T) {
if !isDeferredBootstrapProfileError(errors.New("flag needs an argument: --profile")) {
t.Fatal("missing --profile value must be deferred to the completed Cobra tree")
Expand Down
9 changes: 9 additions & 0 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,15 @@ func buildInternalWithConfig(ctx context.Context, inv cmdutil.InvocationContext,
runtime.skillReferences = skillResolution.References
f.SkillReferences = skillResolution.References

// Global flags and their environment equivalents belong to the same
// distribution capability. Flag tokens are rejected by applyPluginFlagGate;
// install the equivalent guard for an environment-origin profile before
// hooks, Startup, or business commands can observe the invocation.
if installEnvironmentProfileGate(rootCmd, inv, runtime.surface) {
recordInventory(installResult)
return finalizeFailedBuild(runtime, rootCmd)
}

// Install hooks only on business commands. The concealment-specific help
// command is attached afterwards, preserving Cobra's historical contract
// that help is not observed or wrapped by plugins.
Expand Down
18 changes: 15 additions & 3 deletions cmd/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,20 @@ func TestConfigShowRun_NoActiveProfileReturnsStructuredError(t *testing.T) {
if gotCode := output.ExitCodeOf(err); gotCode != output.ExitAuth {
t.Errorf("exit code = %d, want %d", gotCode, output.ExitAuth)
}
if !strings.Contains(err.Error(), "no active profile") {
t.Fatalf("error = %v, want to contain 'no active profile'", err)
// The dangling persisted reference must be named — the generic
// "no active profile" wording hid which input was broken.
if !strings.Contains(err.Error(), `profile "missing" not found`) {
t.Fatalf("error = %v, want the dangling currentApp named", err)
}
var cfgErr *errs.ConfigError
if !errors.As(err, &cfgErr) {
t.Fatalf("expected *errs.ConfigError, got %T", err)
}
if cfgErr.Field != "currentApp" {
t.Errorf("field = %q, want currentApp", cfgErr.Field)
}
if strings.Contains(cfgErr.Hint, "config init") {
t.Errorf("hint = %q, must not suggest config init while intact profiles exist", cfgErr.Hint)
}
}

Expand Down Expand Up @@ -609,7 +621,7 @@ func TestConfigShowRun_ProfileHintUsesBuildLocalSurface(t *testing.T) {
t.Fatal("Render must clone the typed error")
}
if strings.Contains(concealed.Hint, "profile list") ||
!strings.Contains(concealed.Hint, "select or configure an available profile") {
!strings.Contains(concealed.Hint, "select an available profile") {
t.Errorf("concealed hint = %q, want target-free profile recovery", concealed.Hint)
}

Expand Down
6 changes: 3 additions & 3 deletions cmd/config/default_as.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
return err
}

app := multi.CurrentAppConfig(f.Invocation.Profile)
if app == nil {
return core.NoActiveProfileError()
app, err := multi.RequireAppConfig(f.Invocation.Profile, f.Invocation.ProfileSource)
if err != nil {
return err

Check warning on line 30 in cmd/config/default_as.go

View check run for this annotation

Codecov / codecov/patch

cmd/config/default_as.go#L28-L30

Added lines #L28 - L30 were not covered by tests
}

if len(args) == 0 {
Expand Down
33 changes: 15 additions & 18 deletions cmd/config/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/core"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/internal/recovery"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -54,16 +53,9 @@ func configShowRun(opts *ConfigShowOptions) error {
if config == nil || len(config.Apps) == 0 {
return core.NotConfiguredError()
}
app := config.CurrentAppConfig(f.Invocation.Profile)
if app == nil {
hint := recovery.Join("",
recovery.Command(recovery.TargetProfileList, "run: lark-cli profile list")).
WithFallback("select or configure an available profile through this distribution")
return recovery.Annotate(
errs.NewConfigError(errs.SubtypeNotConfigured, "no active profile").
WithHint("%s", hint.String()),
hint,
)
app, err := config.RequireAppConfig(f.Invocation.Profile, f.Invocation.ProfileSource)
if err != nil {
return err
}
users := "(no logged-in users)"
if len(app.Users) > 0 {
Expand All @@ -73,14 +65,19 @@ func configShowRun(opts *ConfigShowOptions) error {
}
users = strings.Join(userStrs, ", ")
}
// profileSource says which channel picked this profile (config | flag |
// environment) — with a session-level LARKSUITE_CLI_PROFILE in play, the
// effective profile and the persisted default can legitimately differ.
_, effectiveSource := config.EffectiveProfile(f.Invocation.Profile, f.Invocation.ProfileSource)
output.PrintJson(f.IOStreams.Out, map[string]interface{}{
"workspace": core.CurrentWorkspace().Display(),
"profile": app.ProfileName(),
"appId": app.AppId,
"appSecret": "****",
"brand": app.Brand,
"lang": app.Lang,
"users": users,
"workspace": core.CurrentWorkspace().Display(),
"profile": app.ProfileName(),
"profileSource": effectiveSource.String(),
"appId": app.AppId,
"appSecret": "****",
"brand": app.Brand,
"lang": app.Lang,
"users": users,
})
fmt.Fprintf(f.IOStreams.ErrOut, "\nConfig file path: %s\n", core.GetConfigPath())
return nil
Expand Down
18 changes: 10 additions & 8 deletions cmd/config/strict_mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,24 @@
}

if reset {
app := multi.CurrentAppConfig(f.Invocation.Profile)
if app == nil {
return core.NoActiveProfileError()
app, err := multi.RequireAppConfig(f.Invocation.Profile, f.Invocation.ProfileSource)
if err != nil {
return err

Check warning on line 48 in cmd/config/strict_mode.go

View check run for this annotation

Codecov / codecov/patch

cmd/config/strict_mode.go#L48

Added line #L48 was not covered by tests
}
return resetStrictMode(f, multi, app, global, args)
}
if len(args) == 0 {
app := multi.CurrentAppConfig(f.Invocation.Profile)
if app == nil {
return core.NoActiveProfileError()
app, err := multi.RequireAppConfig(f.Invocation.Profile, f.Invocation.ProfileSource)
if err != nil {
return err

Check warning on line 55 in cmd/config/strict_mode.go

View check run for this annotation

Codecov / codecov/patch

cmd/config/strict_mode.go#L55

Added line #L55 was not covered by tests
}
return showStrictMode(cmd.Context(), f, multi, app)
}
// --global tolerates a missing profile: the mutation targets the
// shared scope, so only the profile-scoped path requires one.
app := multi.CurrentAppConfig(f.Invocation.Profile)
if !global && app == nil {
return core.NoActiveProfileError()
return multi.ProfileNotFoundError(f.Invocation.Profile, f.Invocation.ProfileSource)

Check warning on line 63 in cmd/config/strict_mode.go

View check run for this annotation

Codecov / codecov/patch

cmd/config/strict_mode.go#L63

Added line #L63 was not covered by tests
}
return setStrictMode(f, multi, app, args[0], global)
},
Expand Down Expand Up @@ -138,7 +140,7 @@
}
} else {
if app == nil {
return core.NoActiveProfileError()
return multi.ProfileNotFoundError(f.Invocation.Profile, f.Invocation.ProfileSource)

Check warning on line 143 in cmd/config/strict_mode.go

View check run for this annotation

Codecov / codecov/patch

cmd/config/strict_mode.go#L143

Added line #L143 was not covered by tests
}
app.StrictMode = &mode
}
Expand Down
15 changes: 15 additions & 0 deletions cmd/doctor/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/larksuite/cli/internal/build"
"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/core"
"github.com/larksuite/cli/internal/envvars"
"github.com/larksuite/cli/internal/identitydiag"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/internal/recovery"
Expand Down Expand Up @@ -130,6 +131,20 @@ func doctorRun(opts *DoctorOptions, projector *recovery.Projector) error {
}
checks = append(checks, pass("app_resolved", fmt.Sprintf("app: %s (%s)", cfg.AppID, cfg.Brand)))

// An external credential provider resolves the account without consulting
// profiles at all, so an explicit selector is silently inert. Say so:
// nothing else in the session will. ProfileName is only populated by the
// built-in config-backed provider, which makes it the provider telltale.
if f.Invocation.Profile != "" && cfg.ProfileName == "" {
selector := "--profile"
if f.Invocation.ProfileSource == core.ProfileFromEnvironment {
selector = envvars.CliProfile
}
checks = append(checks, warn("profile_selector",
fmt.Sprintf("%s=%q is ignored: credentials are provided externally", selector, f.Invocation.Profile),
fmt.Sprintf("unset %s, or remove the external credential variables to select accounts by profile", selector)))
Comment on lines +138 to +145

@coderabbitai coderabbitai Bot Aug 5, 2026

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

Only warn for explicit selector sources.

Invocation.Profile also contains the persisted currentApp. With an external provider, this condition emits profile_selector and says --profile was ignored even when the user supplied no flag or environment selector.

Require ProfileFromFlag or ProfileFromEnvironment before adding this warning. Add a persisted-default regression case that asserts no profile_selector check.

  • cmd/doctor/doctor.go#L138-L145: gate the warning on ProfileFromFlag or ProfileFromEnvironment.
  • cmd/doctor/doctor_test.go#L257-L302: add coverage for a persisted profile source with external credentials.
📍 Affects 2 files
  • cmd/doctor/doctor.go#L138-L145 (this comment)
  • cmd/doctor/doctor_test.go#L257-L302
🤖 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 `@cmd/doctor/doctor.go` around lines 138 - 145, The profile-selector warning in
cmd/doctor/doctor.go must only run when Invocation.ProfileSource is
ProfileFromFlag or ProfileFromEnvironment; update the condition around the
profile_selector check while preserving the existing selector-specific
messaging. In cmd/doctor/doctor_test.go, add a regression case for a persisted
currentApp/profile source with external credentials and assert that no
profile_selector check is produced.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The premise doesn't hold: BootstrapInvocationContext only populates Invocation.Profile for an explicit selector — the persisted-default path returns Profile == "" with ProfileFromConfig (cmd/bootstrap.go), so this warning cannot fire without a flag or environment selector. Added TestDoctorRun_NoSelectorWarningForPersistedDefault in 22c4e51 to pin that contract against future bootstrap changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline cmd/bootstrap.go --items all --type function --match 'BootstrapInvocationContext'
ast-grep outline cmd/doctor/doctor_test.go --items all --type function --match 'TestDoctorRun_NoSelectorWarningForPersistedDefault|TestDoctorRun_WarnsWhenExternalProviderIgnoresProfileSelector'

printf '\n--- Bootstrap implementation ---\n'
rg -n -A90 -B8 'func BootstrapInvocationContext\s*\(' cmd/bootstrap.go

printf '\n--- Persisted-default doctor regression test ---\n'
rg -n -A100 -B8 'func TestDoctorRun_NoSelectorWarningForPersistedDefault\s*\(' cmd/doctor/doctor_test.go

printf '\n--- Doctor warning condition ---\n'
rg -n -A22 -B7 'profile_selector' cmd/doctor/doctor.go

Length of output: 6247


@liangshuo-1, verification confirms your explanation.

BootstrapInvocationContext returns an empty Profile with ProfileFromConfig. The warning requires a non-empty Profile. Therefore, a persisted default cannot produce profile_selector.

TestDoctorRun_NoSelectorWarningForPersistedDefault correctly protects this contract.


🧠 Learnings used
Learnt from: JayYoung2021
Repo: larksuite/cli PR: 410
File: cmd/auth/login_scope_cache.go:47-54
Timestamp: 2026-04-11T23:06:23.250Z
Learning: In larksuite/cli Go code, use `validate.SafeInputPath` only for user-supplied file path inputs (e.g., `--file` flags). Do NOT require `validate.SafeInputPath` for internally constructed, application-managed filesystem paths when: (1) their root directory is already validated via `validate.SafeEnvDirPath()` inside helpers like `appdir.CacheDir()`, `appdir.StateDir()`, or `appdir.LogDir()`, and (2) the filename/path components are sanitized by helpers such as `sanitizeLoginScopeCacheKey()`. In code reviews, missing `SafeInputPath` calls should not be flagged for these app-managed paths if the above safeguards are already in place.

Learnt from: JayYoung2021
Repo: larksuite/cli PR: 410
File: internal/update/update.go:147-147
Timestamp: 2026-04-11T23:06:28.717Z
Learning: In this repo’s Go code (larksuite/cli), treat `validate.SafeInputPath` as a guard for **user-supplied local file paths only**. When you build a path by joining a previously validated base directory (e.g., from `appdir.StateDir()`, `appdir.CacheDir()`, `appdir.ConfigDir()` which already validate env overrides via `validate.SafeEnvDirPath()`) with a compile-time constant filename (e.g., `"update-state.json"`, `"config.json"`), do **not** require an additional `validate.SafeInputPath` call before using `vfs.*` or `validate.AtomicWrite`. Reviewers should avoid flagging/adding redundant `validate.SafeInputPath` in these computed-path cases; only require it when the path ultimately comes from user input.

Learnt from: ViperCai
Repo: larksuite/cli PR: 450
File: shortcuts/slides/slides_create.go:50-60
Timestamp: 2026-04-14T03:13:09.459Z
Learning: When reviewing this Go codebase, do not require explicit `validate.SafeInputPath` / `validate.SafeOutputPath` calls for user-supplied paths if the code uses the `runtime.FileIO()` abstraction methods (`Stat`, `Open`, `Save`, `ResolvePath`). `runtime.FileIO()` is backed by `LocalFileIO` (in `localfileio.go`), which enforces `SafeInputPath`/`SafeOutputPath` before any filesystem access and reports violations as `fileio.PathValidationError`. Only require explicit `validate.SafeInputPath` when bypassing `runtime.FileIO()` and calling `vfs.*` or `os.*` directly with user-influenced paths.

Learnt from: evandance
Repo: larksuite/cli PR: 984
File: internal/lintcheck/scan_test.go:0-0
Timestamp: 2026-05-20T08:44:00.596Z
Learning: In Go, the "declared and not used" compiler error is triggered only for variables that are never referenced after declaration. A statement like `x++` counts as a use, so a variable that is only incremented (e.g., `rejectCount++`) will still compile even if it is never read. When reviewing Go code, don’t assume `x++`-only variables would fail to compile—treat them as potentially semantically dead code instead (remove/avoid unless it’s required for later reads, logging, metrics, or other side effects).

Learnt from: evandance
Repo: larksuite/cli PR: 1135
File: cmd/config/default_as.go:44-50
Timestamp: 2026-05-27T11:31:47.366Z
Learning: In migrated command files (the ones covered by the `errs-typed-only` forbidigo rule in `.golangci.yml`) where `RunE` is expected to use typed errors, `RunE` must return typed `errs.NewXxxError(...)` values directly (return the error value), and must not use `output.Errorf(...)` or `output.ErrWithHint(...)`. The root dispatcher (`cmd/root.go` → `handleRootError`) will route `errs.*` typed errors through `output.WriteTypedErrorEnvelope` and `output.ExitCodeOf`, so direct `errs.*` returns are the correct post-migration stderr JSON contract shape. Only non-migrated/legacy paths should continue to use `output.Errorf` / `output.ErrWithHint`. In migrated command files, do not flag direct `errs.*` returns from `RunE` as stderr JSON contract violations.

Learnt from: evandance
Repo: larksuite/cli PR: 1135
File: cmd/config/keychain_downgrade_other.go:23-25
Timestamp: 2026-05-27T11:31:49.884Z
Learning: For larksuite/cli command files under cmd/**/*.go (starting with migrated commands / PR `#1135` onward), ensure `RunE` returns typed errors directly: return values created by `errs.NewXxxError(...)` (e.g., `errs.NewValidationError`, `errs.NewInternalError`, `errs.NewConfigError`, etc.). Do not return `output.ErrXxx` or `output.ErrWithHint` from `RunE`. The command-layer error dispatcher (`cmd/root.go`’s `handleRootError`) should route `errs.TypedError` / `errs.ProblemOf` through `output.WriteTypedErrorEnvelope` and `output.ExitCodeOf` to produce the typed stderr JSON envelope. Treat direct `errs.NewXxxError` returns in `RunE` as compliant with the new stderr contract (do not flag them); the older pre-migration rule requiring `output.Errorf` / `output.ErrWithHint` no longer applies. This is enforced by the `.golangci.yml` `errs-typed-only` forbidigo rule for migrated paths.

Learnt from: evandance
Repo: larksuite/cli PR: 1135
File: cmd/config/keychain_downgrade.go:57-60
Timestamp: 2026-05-27T11:31:54.717Z
Learning: In larksuite/cli, for migrated code paths, RunE handlers should return typed errors directly (e.g., errs.NewInternalError / errs.NewValidationError / errs.NewAuthenticationError) rather than using the old output.Errorf / output.ErrWithHint pattern. This is the intended post-migration behavior introduced in PR `#1135` (typed envelope contract for auth-domain errors): cmd/root.go’s handleRootError detects errs.* typed errors and dispatches them through output.WriteTypedErrorEnvelope to produce the canonical stderr JSON envelope. Do not flag typed errs.* returns as violations of the old guideline; that old output.Errorf/ErrWithHint guideline applies only to un-migrated paths. The .golangci.yml forbidigo rule errs-typed-only defines which paths are migrated and must use typed errors exclusively—follow that rule when reviewing RunE handlers.

Learnt from: evandance
Repo: larksuite/cli PR: 1449
File: cmd/profile/rename.go:70-71
Timestamp: 2026-06-13T11:20:19.566Z
Learning: In larksuite/cli Go code, when wrapping errors returned by `core.SaveMultiAppConfig` and other local config persistence/save operations, use `errs.NewInternalError(errs.SubtypeStorage, ...)` instead of `errs.SubtypeFileIO`. In this codebase, `errs.SubtypeStorage` is the canonical subtype for “config file save”/local persistence failures, while `errs.SubtypeFileIO` is reserved for general file I/O operations. Do not treat `errs.SubtypeStorage` as an incorrect subtype for config-save paths during code review.

Learnt from: evandance
Repo: larksuite/cli PR: 1449
File: cmd/profile/list.go:49-49
Timestamp: 2026-06-13T11:20:17.330Z
Learning: In the larksuite/cli codebase, when migrating error handling for calls to `core.LoadMultiAppConfig`, don’t add typed passthrough guards like `errs.ProblemOf` / `errs.IsTyped` just to preserve/forward typed errors—`core.LoadMultiAppConfig` only returns raw (untyped) errors, and there’s no typed `errs.*` error to pass through. Also, if the failure-path subtype classification (e.g., `SubtypeFailedPrecondition` vs `SubtypeFileIO`) is already inconsistent as a pre-existing issue elsewhere, don’t block a migration-only PR on that mismatch; keep the PR focused on the migration.

Learnt from: evandance
Repo: larksuite/cli PR: 1449
File: cmd/profile/use.go:70-71
Timestamp: 2026-06-13T11:20:25.527Z
Learning: In larksuite/cli, when an error subtype represents internal persistence of configuration/state (e.g., saving multi-app config via core.SaveMultiAppConfig or similar “config file save” operations), use errs.SubtypeStorage rather than errs.SubtypeFileIO. errs.SubtypeStorage is intended for local persistence failures (config file save is the canonical case). Reserve errs.SubtypeFileIO for user-facing file read/write operations (e.g., reading a user-supplied --file path). Therefore, do not flag errs.SubtypeStorage usage when it occurs on config/state save paths—this is intentional and correct.

Learnt from: wanghm25
Repo: larksuite/cli PR: 2122
File: skills/lark-base/references/lark-base-dashboard-block-get-data.md:66-66
Timestamp: 2026-07-30T13:20:18.339Z
Learning: When reviewing Go code related to lark “base shortcuts” dashboard URL resolution and the `+dashboard-block-get-data` command, do not infer/require a `dsh*` prefix for dashboard IDs from mocked test fixtures. A dashboard should be treated as a top-level `Base` block: `shortcuts/base/base_resolve.go` assigns the selected block ID directly to `dashboard_id` for dashboard URLs. Also ensure `+dashboard-block-get-data` accepts `--dashboard-id` only as a compatibility argument (not as the source of truth for fetching chart data), and that chart data is read using `base_token + block_id`. Documentation/examples should use `blk_xxx` placeholders for `--dashboard-id`.

You are interacting with an AI system.

}

ep := core.ResolveEndpoints(cfg.Brand)

// ── 3. Identity readiness ──
Expand Down
Loading
Loading