larksuite/cli · error
keychain unavailable: %w hint: use file: reference in config
Error message
keychain unavailable: %w hint: use file: reference in config to bypass keychain
What it means
ForStorage stores a plain-text secret into the OS keychain and returns a keychain SecretRef for config.json. This error means the keychain Set operation failed (no keyring service, headless environment, locked keyring, access denied, etc.), and it includes a hint to use a file reference in config to bypass the keychain entirely.
Source
Thrown at internal/core/secret_resolve.go:50
return strings.TrimSpace(string(data)), nil
case "keychain":
return kc.Get(keychain.LarkCliService, s.Ref.ID)
default:
return "", fmt.Errorf("unknown secret source: %s", s.Ref.Source)
}
}
// ForStorage determines how to store a secret in config.json.
// - SecretRef → preserved as-is
// - Plain text → stored in keychain, returns keychain SecretRef
// Returns error if keychain is unavailable (no silent plaintext fallback).
func ForStorage(appId string, input SecretInput, kc keychain.KeychainAccess) (SecretInput, error) {
if !input.IsPlain() {
return input, nil // SecretRef → keep as-is
}
key := secretAccountKey(appId)
if err := kc.Set(keychain.LarkCliService, key, input.Plain); err != nil {
return SecretInput{}, fmt.Errorf("keychain unavailable: %w\nhint: use file: reference in config to bypass keychain", err)
}
return SecretInput{Ref: &SecretRef{Source: "keychain", ID: key}}, nil
}
// ValidateSecretKeyMatch checks that the appSecret keychain key references the
// expected appId. This prevents silent mismatches when config.json is edited by
// hand (e.g. appId changed but appSecret.id still points to the old app).
// Only applicable when appSecret is a keychain SecretRef; other forms are skipped.
func ValidateSecretKeyMatch(appId string, secret SecretInput) error {
if secret.Ref == nil || secret.Ref.Source != "keychain" {
return nil
}
expected := secretAccountKey(appId)
if secret.Ref.ID != expected {
return fmt.Errorf(
"appSecret keychain key %q does not match appId %q (expected %q); %s",
secret.Ref.ID, appId, expected, reconfigureHint(),
)View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Follow the error hint: store the secret in a file and reference it in config.json as {"source":"file","id":"<path>"} instead of a plain string.
- Install/start a keyring service (e.g. gnome-keyring) and unlock it, then retry the command.
- Run the command in a desktop session or environment where a secret service is available over D-Bus.
- In SSH/CI setups, unlock the keyring and export DBUS_SESSION_BUS_ADDRESS before running the CLI.
Example fix
# before: plain secret requires keychain
lark config init --app-secret s3cr3t
# after: file reference bypasses keychain
echo -n 's3cr3t' > ~/.lark/appsecret && chmod 600 ~/.lark/appsecret
# then in config.json: "appSecret": {"source": "file", "id": "/home/me/.lark/appsecret"} Defensive patterns
Strategy: fallback
Validate before calling
// probe keychain availability before passing a plain secret
if err := kc.Set("lark-cli-probe", "lark-cli-probe", "ok"); err != nil {
// keychain unavailable: use a file reference instead of a plain secret
} Try / catch
stored, err := core.ForStorage(appID, input, kc)
if err != nil {
if strings.Contains(err.Error(), "keychain unavailable") {
// fallback: write secret to a 0600 file and use {source: file, id: path}
}
return err
} Prevention
- In headless/CI environments, always use file secret references instead of plain appSecret values.
- Ensure gnome-keyring/kwallet (Linux) or Keychain access (macOS) is installed, running, and unlocked.
- When using SSH, unlock the keyring and export DBUS_SESSION_BUS_ADDRESS before auth commands.
- Follow the error's built-in hint: switch config to a file reference to bypass the keychain.
When it happens
Trigger: Calling ForStorage (via Build, configInitRun, profileAddRun) with a plain (non-ref) appSecret while kc.Set fails - e.g. no keyring daemon, SSH session without an unlocked keyring, unsupported platform, or keychain access denied.
Common situations: Running config init or auth login on a headless Linux server without gnome-keyring/kwallet; CI containers with no secret service on D-Bus; macOS Keychain denying access; WSL without keyring integration.
Related errors
- appSecret must be a string or {source, id} object
- failed to read secret file %s: %w
- unknown secret source: %s
- appSecret keychain key %q does not match appId %q (expected
- invalid %s %q (want true/false/1/0)
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/4e237c475d800698.
Report an issue: GitHub.