Skip to content

feat: support LARKSUITE_CLI_PROFILE env var for multi-app selection#445

Open
meijing0114 wants to merge 2 commits intolarksuite:mainfrom
meijing0114:feat/multi-app-env-selection
Open

feat: support LARKSUITE_CLI_PROFILE env var for multi-app selection#445
meijing0114 wants to merge 2 commits intolarksuite:mainfrom
meijing0114:feat/multi-app-env-selection

Conversation

@meijing0114
Copy link
Copy Markdown

@meijing0114 meijing0114 commented Apr 13, 2026

Summary

  • Add LARKSUITE_CLI_PROFILE environment variable to select the active app profile without the --profile CLI flag
  • Add FindAppByID() and ActiveApp() helpers to internal/core/config.go for env-var-aware profile resolution
  • Wire env var into BootstrapInvocationContext so all commands (including config show, auth logout, auth list, config default-as) respect it

Motivation

When running multiple lark-cli bots on the same machine (e.g. one for an AI agent platform, one for Claude Code), each agent needs its own app configuration. The --profile flag works for interactive use, but agents run in fixed environments where an environment variable is the natural selector.

Resolution Priority

--profile flag > LARKSUITE_CLI_PROFILE env var > config.CurrentApp field > Apps[0]

Usage

# Each agent sets its env var
export LARKSUITE_CLI_PROFILE=my-bot-name    # profile name
export LARKSUITE_CLI_PROFILE=cli_aXXX       # or app ID

# All commands respect it
lark-cli config show
lark-cli auth login
lark-cli api GET /open-apis/...

Backward Compatibility

100%. Users who don't set the env var get identical behavior.

Test plan

  • FindAppByID found/not-found tests
  • ActiveApp with env var / without / not-found tests
  • RequireConfig integration tests with env var selection
  • BootstrapInvocationContext env var fallback + flag-overrides-env tests
  • Full unit test suite (3134 tests across 55 packages)
  • Manual smoke test: config show with/without env var

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added support for configuring a default profile via the LARKSUITE_CLI_PROFILE environment variable.
    • Command-line flags now take precedence over the environment variable when both are specified.
    • Improved error messaging when apps or profiles cannot be found.

meijing0114 and others added 2 commits April 13, 2026 16:29
Use LARKSUITE_CLI_PROFILE (not LARKSUITE_CLI_APP_ID which collides with
the env credential provider) for profile-based app selection. The env var
accepts both profile names and app IDs, consistent with --profile flag.

- Add envvars.CliProfile constant
- Rename standalone FindApp to FindAppByID to avoid shadowing the method
- Use multi.CurrentAppConfig() in ActiveApp for name+appId resolution
- Add test for CurrentApp fallback when env var is unset

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…back

Commands that use f.Invocation.Profile directly (config show, auth
logout, auth list, config default-as) now respect the env var.
Priority: --profile flag > LARKSUITE_CLI_PROFILE env var > (none).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added the size/L Large or sensitive change across domains or core paths label Apr 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

📝 Walkthrough

Walkthrough

The changes implement environment variable-based profile fallback for the CLI. When no --profile flag is provided, the system uses the LARKSUITE_CLI_PROFILE environment variable to select an app profile. This affects the bootstrap layer, config resolution, and app selection logic.

Changes

Cohort / File(s) Summary
Bootstrap Layer
cmd/bootstrap.go, cmd/bootstrap_test.go
Modified BootstrapInvocationContext to read LARKSUITE_CLI_PROFILE environment variable as fallback when globals.Profile is empty. Added test cases verifying env var population and flag-override precedence.
Config Resolution
internal/core/config.go, internal/core/config_test.go
Added FindAppByID to locate apps by ID with error reporting, and ActiveApp to select active app using env var or fallback to first app. Updated ResolveConfigFromMulti to handle profile override priority (flag takes precedence over env var). Comprehensive test coverage including env-var-based selection, fallback behavior, and precedence validation.
Environment Constants
internal/envvars/envvars.go
Added CliProfile exported constant with value "LARKSUITE_CLI_PROFILE".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A whispered word from nature's air,
When flags are few and profiles bare,
The bunny hops to find the way,
Through env vars bright, a clever play!
With fallback paths and app selection true,
The CLI hops to something new!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding LARKSUITE_CLI_PROFILE environment variable support for multi-app selection, which is the primary feature introduced across all modified files.
Description check ✅ Passed The description covers all required sections: Summary explains the feature and motivation, Changes details the implementations, Test Plan lists comprehensive testing with checkmarks, and Related Issues is addressed. The description is thorough and well-organized.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 13, 2026

Greptile Summary

This PR adds LARKSUITE_CLI_PROFILE as an environment variable alternative to --profile for selecting the active app, enabling agent processes to pin a profile without CLI flags. The implementation is solid: the env var is read in BootstrapInvocationContext (so the resolved value flows through the entire factory/credential chain as a normal profile override), and ActiveApp provides a second read as a safety net for callers that bypass bootstrap (e.g. direct RequireConfig calls).

One subtle side-note worth awareness: strictModeToIdentitySupport in internal/credential/default_provider.go (not changed in this PR) uses CurrentAppConfig(profileOverride) directly and doesn't share ActiveApp's env-var path. In the main CLI flow this is harmless — bootstrap always populates the profile before credentials are resolved — but programmatic callers that create DefaultAccountProvider with an empty profile while the env var is set would get credentials from the env-var-selected app but strict-mode bits from the default app.

Confidence Score: 5/5

Safe to merge — all findings are P2 style/cleanup suggestions with no impact on the normal CLI code path.

The env-var plumbing is correct and backward-compatible, test coverage is thorough, and no runtime errors or data-loss risks were identified. Remaining comments are a stale docstring and an unused exported function.

internal/core/config.go — stale docstring on RequireConfigForProfile and unused FindAppByID export.

Important Files Changed

Filename Overview
internal/envvars/envvars.go Adds CliProfile = "LARKSUITE_CLI_PROFILE" constant — trivial, correctly placed alongside existing env-var constants.
cmd/bootstrap.go Clean env-var fallback after flag parsing: reads LARKSUITE_CLI_PROFILE only when --profile flag is absent, correctly implementing the documented flag > env var priority.
cmd/bootstrap_test.go Two new tests cover env-var fallback and flag-overrides-env-var, both using t.Setenv for safe isolation.
internal/core/config.go Adds FindAppByID (unused in production), ActiveApp (reads env var when profileOverride is empty), and refactors ResolveConfigFromMulti to delegate to ActiveApp; stale docstring on RequireConfigForProfile.
internal/core/config_test.go Comprehensive new tests for FindAppByID, ActiveApp, and RequireConfig with env var; old DoesNotUseEnvProfileFallback test correctly replaced to match new intended behavior.

Sequence Diagram

sequenceDiagram
    participant CLI as Execute()
    participant Boot as BootstrapInvocationContext
    participant Env as os.Getenv
    participant Fac as NewDefault(inv)
    participant DAP as DefaultAccountProvider
    participant RCM as ResolveConfigFromMulti
    participant AA as ActiveApp

    CLI->>Boot: args (os.Args[1:])
    Boot->>Boot: parse --profile flag
    alt --profile flag set
        Boot-->>Fac: InvocationContext{Profile: flagValue}
    else flag empty
        Boot->>Env: LARKSUITE_CLI_PROFILE
        Boot-->>Fac: InvocationContext{Profile: envValue or ""}
    end

    Fac->>DAP: NewDefaultAccountProvider(kc, inv.Profile)

    DAP->>RCM: ResolveConfigFromMulti(multi, kc, profile)
    alt profileOverride != ""
        RCM->>RCM: CurrentAppConfig(profileOverride)
    else profileOverride == "" (direct RequireConfig path)
        RCM->>AA: ActiveApp(multi)
        AA->>Env: LARKSUITE_CLI_PROFILE
        alt env var set
            AA-->>RCM: app selected by env var
        else env var empty
            AA-->>RCM: CurrentApp field or Apps[0]
        end
    end
    RCM-->>DAP: CliConfig
Loading

Comments Outside Diff (1)

  1. internal/core/config.go, line 278-279 (link)

    P2 Stale docstring — missing env var in resolution priority

    The comment on RequireConfigForProfile still lists the old three-step priority but omits the newly-added LARKSUITE_CLI_PROFILE step. Anyone reading this comment will have an incomplete picture of how profile selection works.

Reviews (1): Last reviewed commit: "feat: bootstrap reads LARKSUITE_CLI_PROF..." | Re-trigger Greptile

Comment on lines +90 to +108
// FindAppByID searches apps by appId and returns a pointer to the matching AppConfig.
// Returns a ConfigError listing available IDs if not found.
func FindAppByID(apps []AppConfig, appID string) (*AppConfig, error) {
for i := range apps {
if apps[i].AppId == appID {
return &apps[i], nil
}
}
ids := make([]string, len(apps))
for i, a := range apps {
ids[i] = a.AppId
}
return nil, &ConfigError{
Code: 2,
Type: "config",
Message: fmt.Sprintf("app %q not found in config; available: %s", appID, strings.Join(ids, ", ")),
Hint: "check LARKSUITE_CLI_PROFILE or run `lark-cli config init`",
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 FindAppByID is exported but has no production callers

FindAppByID is only referenced in its own unit tests. The existing MultiAppConfig.FindApp already handles lookup-by-appId (as a second-pass after name matching). If this is scaffolding for future use, a brief comment would help; if it is not needed, removing it would keep the public API surface minimal.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/bootstrap.go`:
- Around line 31-35: The profile value extracted into InvocationContext.Profile
is being lost because RegisterGlobalFlags uses fs.StringVar(&opts.Profile,
"profile", "", ...) which overwrites the pre-set opts.Profile with an empty
default; update the flag registration so the default value is the current
opts.Profile (e.g. fs.StringVar(&opts.Profile, "profile", opts.Profile, ...))
when creating GlobalOptions in root initialization (ensure the
GlobalOptions/Profile passed from bootstrap is preserved and not clobbered by
the flag registration in RegisterGlobalFlags).

In `@internal/core/config.go`:
- Around line 128-131: Current logic treats a nil return from
multi.CurrentAppConfig("") as "no apps configured", which is misleading when
Apps is non-empty but CurrentApp refers to a deleted profile; update the branch
around CurrentAppConfig("") to detect this case: if multi.CurrentAppConfig("")
returns nil and len(multi.Apps) > 0 (or multi.CurrentApp != ""), return a
ConfigError with a distinct message (e.g., "current app profile not found") and
a hint guiding the user to select or re-create the profile (instead of "no apps
configured"); reference the CurrentAppConfig function and the
multi.CurrentApp/multi.Apps fields to locate where to add this conditional and
new ConfigError.
🪄 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

Run ID: a13ec1b5-cedf-407b-960a-dfd6d398c2b3

📥 Commits

Reviewing files that changed from the base of the PR and between bb79572 and 79cf353.

📒 Files selected for processing (5)
  • cmd/bootstrap.go
  • cmd/bootstrap_test.go
  • internal/core/config.go
  • internal/core/config_test.go
  • internal/envvars/envvars.go

Comment on lines +31 to +35
profile := globals.Profile
if profile == "" {
profile = os.Getenv(envvars.CliProfile)
}
return cmdutil.InvocationContext{Profile: profile}, 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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In github.com/spf13/pflag, does FlagSet.StringVar set the destination variable to the provided default value immediately when the flag is registered?

💡 Result:

Yes, in github.com/spf13/pflag, FlagSet.StringVar sets the destination variable to the provided default value immediately when the flag is registered.

Citations:


🏁 Script executed:

#!/bin/bash
# Examine the relevant files to trace the profile initialization flow

echo "=== cmd/bootstrap.go (lines 31-35) ==="
sed -n '25,40p' cmd/bootstrap.go

echo ""
echo "=== cmd/root.go (to see where GlobalOptions is initialized) ==="
grep -n "GlobalOptions" cmd/root.go | head -20

echo ""
echo "=== Full cmd/root.go around line 92-110 ==="
sed -n '85,115p' cmd/root.go

echo ""
echo "=== cmd/global_flags.go (RegisterGlobalFlags function) ==="
cat cmd/global_flags.go

echo ""
echo "=== cmd/bootstrap.go (full context) ==="
cat cmd/bootstrap.go

Repository: larksuite/cli

Length of output: 3125


The env-selected profile is overwritten when flags are registered in root.go.

The bootstrap correctly extracts the profile from the environment and passes it via inv.Profile. However, in cmd/root.go, a new GlobalOptions is created with that profile value, then immediately passed to RegisterGlobalFlags. When RegisterGlobalFlags calls fs.StringVar(&opts.Profile, "profile", "", ...), the hardcoded "" default immediately overwrites opts.Profile back to empty. Any code that later reads globals.Profile will miss the environment variable.

Fix
diff --git a/cmd/global_flags.go b/cmd/global_flags.go
@@
 func RegisterGlobalFlags(fs *pflag.FlagSet, opts *GlobalOptions) {
-	fs.StringVar(&opts.Profile, "profile", "", "use a specific profile")
+	fs.StringVar(&opts.Profile, "profile", opts.Profile, "use a specific profile")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/bootstrap.go` around lines 31 - 35, The profile value extracted into
InvocationContext.Profile is being lost because RegisterGlobalFlags uses
fs.StringVar(&opts.Profile, "profile", "", ...) which overwrites the pre-set
opts.Profile with an empty default; update the flag registration so the default
value is the current opts.Profile (e.g. fs.StringVar(&opts.Profile, "profile",
opts.Profile, ...)) when creating GlobalOptions in root initialization (ensure
the GlobalOptions/Profile passed from bootstrap is preserved and not clobbered
by the flag registration in RegisterGlobalFlags).

Comment on lines +128 to +131
// Fall back to CurrentAppConfig's own resolution (CurrentApp field > Apps[0]).
app := multi.CurrentAppConfig("")
if app == nil {
return nil, &ConfigError{Code: 2, Type: "config", Message: "no apps configured", Hint: "run `lark-cli config init`"}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t collapse a stale currentApp into no apps configured.

When Apps is non-empty and CurrentApp points to a deleted profile, CurrentAppConfig("") returns nil intentionally. This branch now reports "no apps configured", which is misleading and hides the actual remediation path.

Suggested fix
  // Fall back to CurrentAppConfig's own resolution (CurrentApp field > Apps[0]).
  app := multi.CurrentAppConfig("")
  if app == nil {
-		return nil, &ConfigError{Code: 2, Type: "config", Message: "no apps configured", Hint: "run `lark-cli config init`"}
+		return nil, &ConfigError{
+			Code:    2,
+			Type:    "config",
+			Message: fmt.Sprintf("profile %q not found", multi.CurrentApp),
+			Hint:    fmt.Sprintf("available profiles: %s", formatProfileNames(multi.ProfileNames())),
+		}
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/config.go` around lines 128 - 131, Current logic treats a nil
return from multi.CurrentAppConfig("") as "no apps configured", which is
misleading when Apps is non-empty but CurrentApp refers to a deleted profile;
update the branch around CurrentAppConfig("") to detect this case: if
multi.CurrentAppConfig("") returns nil and len(multi.Apps) > 0 (or
multi.CurrentApp != ""), return a ConfigError with a distinct message (e.g.,
"current app profile not found") and a hint guiding the user to select or
re-create the profile (instead of "no apps configured"); reference the
CurrentAppConfig function and the multi.CurrentApp/multi.Apps fields to locate
where to add this conditional and new ConfigError.

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

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant